数据分析之留存分析报告搭建

对用户进行分析过程中,留存是一个重要的指标,它体现了新用户/会员在经过一定时间之后,仍然具有访问、登陆、使用或者转化等属性或行为。用户的留存才有可能产生收入,同时作为监控产品的指标也是需要进行周期性分析。

1. 留存率指标

留存率的分析需要依赖于分析的周期,因此在常用的周期上主要分为了三类:日留存、周留存以及月留存。

1.1 日留存

日留存在具体使用上会根据不同的业务需要使用不同的时间间隔,因此在详细级别上分为:

  • 次日留存率,第一天新增用户中,在第二天继续访问用户除以第一天新增用户数
  • 第三日留存率,第一天新增用户中,在第三天继续访问用户除以第一天新增用户数
  • 第七日留存率,第一天新增用户中,在第七天继续访问用户除以第一天新增用户数
  • 第十四日留存率,第一天新增用户中,在第十四天继续访问用户除以第一天新增用户数
  • 第三十日留存率,第一天新增用户中,在第三十天继续访问用户除以第一天新增用户数

1.2 周留存

周留存统计,也依赖于分析的分析的时间间隔,详细级别上分为:

  • 一周后留存率,第一周新增用户,在第二周继续访问用户除以第一周新增用户数
  • 两周后留存率,第一周新增用户,在第三周继续访问用户除以第一周新增用户数
  • 三周后留存率,第一周新增用户,在第四周继续访问用户除以第一周新增用户数

1.3 月留存

月留存,分析当前月份相对于统计月份上人数的比例。针对于月留存率分析,使用矩阵式表现是一种比较不错的方式:

2. 应用分析

三类留存的分析应用上,日留存分析短期效果,周留存体现中期效果,月留存用于分析长期效果。留存分析需要特别注意衰减比率,一般情况下留存会随着时间变化而递减,可能表现出线性、指数递减。通过递减数据搭建衰减模型可以用于异常检测,例如发现衰减异常发生的时间。

3. 应用实践——月留存报告

3.1 背景

短视频产品新上线,需要了解用户在新产品上留存率的情况。

3.2 数据

数据使用用户播放记录数据。目前业务上短视频是一个独立的栏目专区,在流程上没有独立的登陆验证机制,因此和运营人员沟通确认之后采取了以最低播放时长作为判断该用户是否有使用短视频产品。

  • 判断依据:播放记录的播放时长达到 5s 以上
  • 日期类型解析:需要对表中播放开始、结束时间(begin_time, end_time)以及创建时间(create_time)解析为时间戳
  • 起始时间戳缺失,确认是初期上线之后出现 bug。排除缺失数据之前的数据

时间解析之后,起始时间戳和结束时间戳存在错误,沟通确认该问题是前端数据收集时出现的问题,对解析播放时长(结束时间减去起始时间没有影响)。对于用户的交互时间,可以直接以 create_time 作为参考

3.3 数据清理

因此整个数据清理的内容包括:

  1. 起始时间缺失的情况下,删除最大 create_time 以前的数据
  2. 以时间起讫建立用户观看时长
  3. create_time 字段建立用户交互日期信息
  4. 删除用户播放时长低于 5s 的记录

数据处理的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 筛选出缺失的起始时间戳下的最大时间
time_threshold = data.loc[data.begin_time.isnull(), "create_time"].max()
data = data.loc[data.create_time > time_threshold, :].reset_index(drop=True)

# 时间处理
data['duration']= data.apply(lambda x: (x['end_time'] - x['begin_time']).seconds, axis=1).astype("int32")
data['date'] = pd.to_datetime(data.create_time.dt.date)

# 转换日期数据为月份
data["month"] = data.date.apply(lambda x: x.strftime("%Y-%m"))

# 删除不符合要求的播放时长记录
data = data.loc[data.duration >= 5].reset_index(drop=True)

3.3 月留存分析报告

分析各个月份的留存情况,需要拆分出各个月份的数据。拆分计算之后在聚合报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def date_pair(dates):
"""日期配对生成器

将日期序列进行配对生成当前日期及其之后的日期配对信息,同时保留最大长度的日期长度,不能配
对的信息以 None 补齐

Args:
-------
dates: list,日期序列数据

Examples:
-------
>>> dates = ['2020-12', '2021-01', '2021-02']
>>> for date1, date2 in date_pair(dates):
>>> print(date1, date2)
2020-12 2020-12
2020-12 2021-01
2020-12 2021-02
2021-01 2021-01
2021-01 2021-02
None None
2021-02 2021-02
None None
None None
"""
dates = sorted(dates)
for index in range(len(dates)):
for elem in dates[index:]:
yield dates[index], elem
# 优选补齐当前日期下的信息
for _ in range(index):
yield None, None

def extract_monthly_rention(data, dates, groups, padding="-%", full="100%", ucol="user_id"):
"""计算月度用户留存率

计算各月用户的留存率

Args:
------
data: pd.DataFrame, 用户数据,包括发生交互的时间戳信息
dates: list, 需要统计的时间列表,各个元素为需要统计的时间信息
groups: dict,对需要统计的时间信息进行 GROUP BY 之后,生成的字典信息
padding: str,用于填充需要补齐的时间留存率,默认为 '-%'
full: str, 用于填充当前月份下的留存率,默认为 '100%'
ucol: str, 表示的是 data 数据中的用户字段名称
"""
result = []
login_user = set() # 登陆用户
date = None
element = []
for index, (current, query) in enumerate(date_pair(dates)):
# 更新在新日期下的新用户,以及合并已登陆用户
if date is None or (current is not None and date != current):
new_user = set(data.loc[groups[current], ucol].unique()) - login_user
login_user |= set(data.loc[groups[current], ucol].unique())
date = current

# 如果 element 有元素时,更新结果数据
if element:
result.append(element)
element = []

# 如果当前日期和查询日期相同,是当前月份留存
if query is not None and current == query:
element.append(full)

# 如果是补齐的日期对信息,直接填充数据值
elif query is None:
element.append(padding)
# 其他情况时,直接查询数据计算留存
else:
active = new_user & set(data.loc[groups[query], ucol].unique())
element.append("{0:.2f}%".format(len(active) / len(new_user) * 100))
else:
result.append(element)
# 生成报告
columns = [f"留存率_{i}" for i in range(len(dates))]
report = pd.DataFrame(data=result, columns=columns, index=dates)
return report

# 处理需要使用到的参数
padding = "-%" # 填充缺失的数据
full = "100%" # 首月留存
ucol = "user_id" # 用户字段名称

dates = data.month.unique().tolist()
groups = data.groupby("month").groups

# 运行结果
extract_monthly_rention(data, dates, groups)
作者

ZenRay

发布于

2021-02-24

更新于

2021-04-19

许可协议

CC BY-NC-SA 4.0